package per.jdz.retryer.config;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import per.jdz.retryer.core.Retryer;
import per.jdz.retryer.core.RetryerBuilder;
import per.jdz.retryer.listener.RetryerListener;
import per.jdz.retryer.predicate.Predicates;
import per.jdz.retryer.proxy.ProxyFactory;
import per.jdz.retryer.proxy.RetryerProxy;
import per.jdz.retryer.strategy.AbstractRetryerStrategy;
import per.jdz.retryer.strategy.ExponentialRetryerStrategy;

@Component
public class RetryerPostProcessor implements BeanPostProcessor, ApplicationContextAware {

    private ApplicationContext applicationContextAware;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContextAware = applicationContext;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        final Class<?> beanClass = bean.getClass();
        HashMap<Integer, Retryer> retryerMap = new HashMap<>();
        // 遍历每个方法
        for (Method method : beanClass.getMethods()) {
            Retryer retryer = null;
            try {
                retryer = defaultRetryerStrategyMatchRule(method);
                if (retryer != null) {
                    retryerMap.put(method.hashCode(), retryer);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        // 生成代理对象
        if (!retryerMap.isEmpty()) {
            final RetryerProxy retryerProxy = new RetryerProxy(bean, retryerMap);
            return ProxyFactory.createProxy(bean, retryerProxy);
        }
        return bean;
    }

    protected Retryer defaultRetryerStrategyMatchRule(Method method) throws Exception {
        AbstractRetryerStrategy retryerStrategy = null;
        if (method.isAnnotationPresent(per.jdz.retryer.annotation.Retryer.class)) {
            final per.jdz.retryer.annotation.Retryer retryerAnnotation = method
                    .getAnnotation(per.jdz.retryer.annotation.Retryer.class);
            final int maxAttempts = retryerAnnotation.maxAttempts();
            final int time = retryerAnnotation.time();
            final TimeUnit timeUnit = retryerAnnotation.timeUnit();
            final String[] result = retryerAnnotation.result();
            final Class[] resultClass = retryerAnnotation.resultClass();
            final String listerName = retryerAnnotation.listenerName();
            if (result.length != resultClass.length) {
                throw new Exception("重试结果和重试结果类型长度不匹配");
            }
            final Class<? extends Throwable>[] throwsExp = retryerAnnotation.exception();
            final String async = retryerAnnotation.async();
            retryerStrategy = new ExponentialRetryerStrategy(maxAttempts, time, timeUnit);

            final Predicate[] resulPredicates = new Predicate[result.length];
            for (int i = 0; i < result.length; i++) {
                try {
                    Object newInstance = resultClass[i].getConstructor(String.class).newInstance(result[i]);
                    resulPredicates[i] = Predicates.equalTo(newInstance);
                } catch (InstantiationException e) {
                    throw new RuntimeException(e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                } catch (InvocationTargetException e) {
                    throw new RuntimeException(e);
                } catch (NoSuchMethodException e) {
                    throw new RuntimeException(e);
                }
            }
            final Predicate resultPredicateObject = Predicates.or(resulPredicates);
            final Predicate[] throwsPredicates = new Predicate[throwsExp.length];
            for (int i = 0; i < throwsPredicates.length; i++) {
                throwsPredicates[i] = Predicates.throwTo(throwsExp[i]);
            }
            final Predicate throwsResultPredicateObject = Predicates.or(throwsPredicates);
            Object threadPoll = null;
            if (!async.equals("")) {
                threadPoll = applicationContextAware.getBean(async);
            }
            Object listerBean = null;
            if (!listerName.equals("")) {
                listerBean = applicationContextAware.getBean(listerName);
            }
            if (listerBean != null && !(listerBean instanceof RetryerListener)) {
                throw new ClassCastException(listerBean + "不是 RetryerLister 子类");
            }

            if (threadPoll != null && !(threadPoll instanceof Executor)) {
                throw new ClassCastException(threadPoll + "不是 Executor 子类");

            }
            return RetryerBuilder.newBuilder()
                    .withRetryerStrategy(retryerStrategy)
                    .retryerIfResult(resultPredicateObject)
                    .retryerIfThrowsException(throwsResultPredicateObject)
                    .retryerLister((RetryerListener) listerBean)
                    .retryerExecStrategy((Executor) threadPoll)
                    .build();
        }
        return null;
    }
}
